﻿/**
 * Tencent is pleased to support the open source community by making Tars available.
 *
 * Copyright (C) 2016THL A29 Limited, a Tencent company. All rights reserved.
 *
 * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except
 * in compliance with the License. You may obtain a copy of the License at
 *
 * https://opensource.org/licenses/BSD-3-Clause
 *
 * Unless required by applicable law or agreed to in writing, software distributed
 * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
 * CONDITIONS OF ANY KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations under the License.
 */
#include "util/tc_logger.h"
#include <iostream>
#include <string.h>
#include <mutex>

using namespace std;

namespace tars
{

	bool TC_LoggerRoll::_bDyeingFlag = false;
	TC_SpinLock TC_LoggerRoll::_mutexDyeing;
	unordered_map<size_t, string>  TC_LoggerRoll::_mapThreadID;

	const string LogByDay::FORMAT = "%Y%m%d";
	const string LogByHour::FORMAT = "%Y%m%d%H";
	const string LogByMinute::FORMAT = "%Y%m%d%H%M";

	void TC_LoggerRoll::setupThread(TC_LoggerThreadGroup* pThreadGroup)
	{
		assert(pThreadGroup != NULL);

		unSetupThread();

		std::lock_guard<std::mutex> lock(_mutex);

		_pThreadGroup = pThreadGroup;

		TC_LoggerRollPtr self = this;

		_pThreadGroup->registerLogger(self);
	}

	void TC_LoggerRoll::unSetupThread()
	{
		std::lock_guard<std::mutex> lock(_mutex);

		if (_pThreadGroup != NULL)
		{
			_pThreadGroup->flush();

			TC_LoggerRollPtr self = this;

			_pThreadGroup->unRegisterLogger(self);

			_pThreadGroup = NULL;
		}

		flush();
	}

	void TC_LoggerRoll::write(const pair<std::size_t, string>& buffer)
	{
		size_t ThreadID = 0;
		if (_bDyeingFlag)
		{
			TC_LockT<TC_SpinLock> lock(_mutexDyeing);

			if (_mapThreadID.find(TC_Thread::CURRENT_THREADID()) != _mapThreadID.end())
			{
				ThreadID = TC_Thread::CURRENT_THREADID();
			}
		}

		if (_pThreadGroup)
		{
			_buffer.push_back(make_pair(ThreadID, buffer.second));
		}
		else
		{
			//同步记录日志
			deque<pair<size_t, string> > ds;
			ds.push_back(make_pair(ThreadID, buffer.second));
			roll(ds);
		}
	}

	void TC_LoggerRoll::flush()
	{
		TC_CasQueue<pair<size_t, string> >::queue_type qt;

		_buffer.swap(qt);

		if (!qt.empty())
		{
			roll(qt);
		}
	}

	//////////////////////////////////////////////////////////////////
	//
	TC_LoggerThreadGroup::TC_LoggerThreadGroup() : _bTerminate(false), _thread(NULL)
	{
	}

	TC_LoggerThreadGroup::~TC_LoggerThreadGroup()
	{
		terminate();
		/*挪到terminate函数中
		flush();

		{
			std::lock_guard<std::mutex> guard(_mutex);
			_bTerminate = true;
			_cond.notify_all();
		}

		if(_thread)
		{
			_thread->join();
			delete _thread;
			_thread = NULL;
		}
		*/
	}

	void TC_LoggerThreadGroup::start(size_t iThreadNum)
	{
		if (_thread == NULL)
		{
			_thread = new std::thread(&TC_LoggerThreadGroup::run, this);
		}
	}

	void TC_LoggerThreadGroup::registerLogger(TC_LoggerRollPtr& l)
	{
		std::lock_guard<std::mutex> lock(_mutex);

		_logger.insert(l);
	}

	void TC_LoggerThreadGroup::unRegisterLogger(TC_LoggerRollPtr& l)
	{
		std::lock_guard<std::mutex> lock(_mutex);

		_logger.erase(l);
	}

	void TC_LoggerThreadGroup::terminate()
	{
		if (_bTerminate)
			return;

		flush();

		{
			std::unique_lock<std::mutex> lock(_mutex);
			_bTerminate = true;
			_cond.notify_all();
		}

		if (_thread)
		{
			_thread->join();
			delete _thread;
			_thread = NULL;
		}
	}

	void TC_LoggerThreadGroup::flush()
	{
		logger_set logger;

		{
			std::lock_guard<std::mutex> lock(_mutex);
			logger = _logger;
		}

		logger_set::iterator it = logger.begin();
		while (it != logger.end())
		{
			try
			{
				it->get()->flush();
			}
			catch (exception& ex)
			{
				cerr << "[TC_LoggerThreadGroup::flush] log flush error:" << ex.what() << endl;
			}
			catch (...)
			{
			}
			++it;
		}
	}

	void TC_LoggerThreadGroup::run()
	{
		while (!_bTerminate)
		{
			//100ms
			{
				std::unique_lock<std::mutex> lock(_mutex);
				_cond.wait_for(lock, std::chrono::milliseconds(100));
			}

			flush();
		}
	}

	//////////////////////////////////////////////////////////////////////////////////

	LoggerBuffer::LoggerBuffer() : _buffer(NULL), _buffer_len(0)
	{
	}

	LoggerBuffer::LoggerBuffer(TC_LoggerRollPtr roll, size_t buffer_len) : _roll(roll), _buffer(NULL), _buffer_len(buffer_len)
	{
		//设置get buffer, 无效, 不适用
		setg(NULL, NULL, NULL);

		//设置put buffer
		if (_roll)
		{
			//分配空间
			_buffer = new char[_buffer_len];
			setp(_buffer, _buffer + _buffer_len);
		}
		else
		{
			setp(NULL, NULL);
			_buffer_len = 0;
		}
	}

	LoggerBuffer::~LoggerBuffer()
	{
		sync();
		if (_buffer)
		{
			delete[] _buffer;
		}
	}

	streamsize LoggerBuffer::xsputn(const char_type* s, streamsize n)
	{
		if (!_roll)
		{
			return n;
		}

		return std::basic_streambuf<char>::xsputn(s, n);
	}

	void LoggerBuffer::reserve(std::streamsize n)
	{
		if (n <= _buffer_len)
		{
			return;
		}

		//不超过最大大小
		if (n > MAX_BUFFER_LENGTH)
		{
			n = MAX_BUFFER_LENGTH;
		}

		int64_t len = pptr() - pbase();
		char_type* p = new char_type[n];
		memcpy(p, _buffer, len);
		delete[] _buffer;
		_buffer = p;
		_buffer_len = n;

		setp(_buffer, _buffer + _buffer_len);

		pbump((int)len);

		return;
	}

	std::basic_streambuf<char>::int_type LoggerBuffer::overflow(std::basic_streambuf<char>::int_type c)
	{
		if (!_roll)
		{
			return 0;
		}

		if (_buffer_len >= MAX_BUFFER_LENGTH)
		{
			sync();
		}
		else
		{
			reserve(_buffer_len * 2);
		}

		if (std::char_traits<char_type>::eq_int_type(c, std::char_traits<char_type>::eof()))
		{
			return std::char_traits<char_type>::not_eof(c);
		}
		else
		{
			return sputc(c);
		}
		return 0;
	}

	int LoggerBuffer::sync()
	{
		//有数据
		if (pptr() > pbase())
		{
			std::streamsize len = pptr() - pbase();

			if (_roll)
			{
				//具体的写逻辑
				_roll->write(make_pair(TC_Thread::CURRENT_THREADID(), string(pbase(), len)));
			}

			//重新设置put缓冲区, pptr()重置到pbase()处
			setp(pbase(), epptr());
		}
		return 0;
	}

	////////////////////////////////////////////////////////////////////////////////////
	//

	TC_RollBySizeW::TC_RollBySizeW()
		: _maxSize(5000000)
		, _maxNum(10)
		, _iUpdateCount(0)
		, _lt(time(NULL))
	{
	}

	TC_RollBySizeW::~TC_RollBySizeW()
	{
		if (_of.is_open())
		{
			_of.close();
		}
	}

	void TC_RollBySizeW::init(const string& path, int iMaxSize, int iMaxNum)
	{
		std::lock_guard<std::mutex> lock(*this);

		_path = path;
		_maxSize = iMaxSize;
		_maxNum = iMaxNum;
	}

	string TC_RollBySizeW::getPath()
	{
		std::lock_guard<std::mutex> lock(*this);
		return _path;
	}

	void TC_RollBySizeW::setPath(const string& path)
	{
		std::lock_guard<std::mutex> lock(*this);
		_path = path;
	}

	int TC_RollBySizeW::getMaxSize()
	{
		std::lock_guard<std::mutex> lock(*this);
		return _maxSize;
	}

	void TC_RollBySizeW::setMaxSize(int maxSize)
	{
		std::lock_guard<std::mutex> lock(*this);
		_maxSize = maxSize;
	}

	int TC_RollBySizeW::getMaxNum()
	{
		std::lock_guard<std::mutex> lock(*this);
		return _maxNum;
	}

	void TC_RollBySizeW::setMaxNum(int maxNum)
	{
		std::lock_guard<std::mutex> lock(*this);
		_maxNum = maxNum;
	}

	void TC_RollBySizeW::roll(const deque<pair<size_t, string> >& buffer)
	{
		std::lock_guard<std::mutex> lock(*this);

		if (_path.empty())
		{
			_write(cout, buffer);
			return;
		}

		time_t t = TNOW;
		time_t tt = t - _lt;
		//每隔5, 重新打开一次文件, 避免文件被删除后句柄不释放
		//Every 5, reopen the file to prevent the handle from being released after the file is deleted.
		if (tt > 5 || tt < 0)
		{
			_lt = t;
			_of.close();
		}

		//检查日志文件是否打开
		//Check that the log file is open
		if (!_of.is_open())
		{
			string sLogFileName = _path + ".log";

			_of.open(sLogFileName.c_str(), ios::app | ios::out);

			string sLogFilePath = TC_File::extractFilePath(_path);

			if (!TC_File::isFileExist(sLogFilePath, (mode_t)S_IFREG))
			{
				TC_File::makeDirRecursive(sLogFilePath);
			}

			if (!_of.is_open())
			{
				// cout << "write roll4:" << sLogFileName << ", " << strerror(errno)<< endl;

				//抛异常前继续进入_t 以便打远程日志
				//Continue to _t for remote logging before throwing an exception
				_write(_of, buffer);
				THROW_EXCEPTION_SYSCODE(TC_Logger_Exception, "[TC_RollBySize::roll]:fopen fail: " + sLogFileName);
				// throw TC_Logger_Exception("[TC_RollBySize::roll]:fopen fail: " + sLogFileName, TC_Exception::getSystemCode());
			}
		}

		_write(_of, buffer);

		if (tt <= 5)
		{
			return;
		}

		//文件大小小于限制, 直接返回
		//File size less than limit, return directly
		if (_of.tellp() < _maxSize)
		{
			return;
		}

		//文件大小超出限制,删除最后一个文件
		//File size exceeds limit, delete last file
		string sLogFileName = _path + TC_Common::tostr(_maxNum - 1) + ".log";
		{
			if (TC_File::isFileExist(sLogFileName, (mode_t)S_IFREG) && TC_File::removeFile(sLogFileName, true))
			{
				return;
			}
		}

		//需要先关闭当前文件，再进行rename操作
		_of.close();

		//将log文件命名shift, xxx1.log=>xxx2.log,第一文件还是为xxx.log
		//Name the log file shift, xxx1.log=>xxx2.log, or the first file is xxx.log
		for (int i = _maxNum - 2; i >= 0; i--)
		{
			if (i == 0)
			{
				sLogFileName = _path + ".log";
			}
			else
			{
				sLogFileName = _path + TC_Common::tostr(i) + ".log";
			}

			if (TC_File::isFileExist(sLogFileName, (mode_t)S_IFREG))
			{
				string sNewLogFileName = _path + TC_Common::tostr(i + 1) + ".log";
				rename(sLogFileName.c_str(), sNewLogFileName.c_str());
			}
		}

		_of.open(sLogFileName.c_str(), ios::app);
		if (!_of)
		{
			THROW_EXCEPTION_SYSCODE(TC_Logger_Exception, "[TC_RollBySize::roll]:fopen fail: " + sLogFileName);
			// throw TC_Logger_Exception("[TC_RollBySize::roll]:fopen fail: " + sLogFileName, TC_Exception::getSystemCode());
		}
	}

	////////////////////////////////////////////////////////////////////////////////////
	//
	TC_RollByTimeW::TC_RollByTimeW()
		: _lt(0)
		, _logTypePtr(NULL)
		, _bRemoteType(false)
	{
		_lt = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
		_bHasSufix = true;
		_sConcatStr = "_";
	}

	TC_RollByTimeW::~TC_RollByTimeW()
	{
		if (_of.is_open())
		{
			_of.close();
		}
	}

	void TC_RollByTimeW::init(const string& path, const string& format, bool bHasSufix, const string& sConcatstr, const LogTypePtr& logTypePtr, bool bIsRemote)
	{
		std::lock_guard<std::mutex> lock(*this);

		_logTypePtr = logTypePtr;
		_path = path;
		_format = format;
		_bHasSufix = bHasSufix;
		_sConcatStr = sConcatstr;
		_bRemoteType = bIsRemote;

		if (_logTypePtr)
		{
			_currentTime = logTypePtr->get_init_time();
		}
		else
		{
			_currentTime = TC_Common::now2str(_format);
		}
	}

	string TC_RollByTimeW::getPath()
	{
		std::lock_guard<std::mutex> lock(*this);
		return _path;
	}

	void TC_RollByTimeW::setPath(const string& path)
	{
		std::lock_guard<std::mutex> lock(*this);
		_path = path;
	}

	string TC_RollByTimeW::getFormat()
	{
		std::lock_guard<std::mutex> lock(*this);
		return _format;
	}

	void TC_RollByTimeW::setFormat(const string& format)
	{
		std::lock_guard<std::mutex> lock(*this);
		_format = format;
	}

	void TC_RollByTimeW::setRemote(bool en)
	{
		_bRemoteType = en;
	}

	void TC_RollByTimeW::roll(const deque<pair<size_t, string> >& buffer)
	{
		std::lock_guard<std::mutex> lock(*this);

		if (_path.empty())
		{
			_write(cout, buffer);
			return;
		}
		//远程日志在本地不用打开文件
		//Remote log does not need to open files locally
		if (_bRemoteType)
		{
			_write(_of, buffer);
			return;
		}

		time_t t = TNOW;//std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());

		string nowTime = "";
		//如果设置了记录类型,则使用记录类型来计算时间
		//If record type is set, use record type to calculate time
		if (_logTypePtr)
		{
			nowTime = _logTypePtr->get_new_time(t);
			//时间跨越了
			//Time has crossed
			if (nowTime != "")
			{
				_currentTime = nowTime;
				_of.close();
			}
			else
			{
				nowTime = _currentTime;
			}
		}
		else
		{
			nowTime = TC_Common::tm2str(t, _format);
			//检查时间是否跨时间了, 跨时间后关闭文件
			//Check if time is crossed and close the file after crossing time
			if (_currentTime != nowTime)
			{
				_currentTime = nowTime;
				_of.close();
			}
		}

		//每隔10s, 重新打开一次文件, 避免文件被删除后句柄不释放
		//Reopen the file every 10s to prevent the handle from being released after the file is deleted
		if (t - _lt > 10 || t - _lt < 0)
		{
			_lt = t;
			_of.close();
		}

		if (!_of.is_open())
		{
			string sLogFileName = _path + _sConcatStr + nowTime + (_bHasSufix ? ".log" : "");
			string sLogFilePath = TC_File::extractFilePath(_path);
			if (!TC_File::isFileExist(sLogFilePath, (mode_t)S_IFREG))
			{
				TC_File::makeDirRecursive(sLogFilePath);
			}

			_of.open(sLogFileName.c_str(), ios::app);

			if (!_of)
			{
				//抛异常前继续进入_t 以便打远程日志
				//Continue to _t for remote logging before throwing an exception
				_write(_of, buffer);
				THROW_EXCEPTION_SYSCODE(TC_Logger_Exception, "[TC_RollByTime::roll]:fopen fail: " + sLogFileName);
				// throw TC_Logger_Exception("[TC_RollByTime::roll]:fopen fail: " + sLogFileName, TC_Exception::getSystemCode());
			}
		}

		//写
		//write
		_write(_of, buffer);
	}
}
